<template>
  <div>
    <el-drawer
      v-bind="$attrs"
      v-on="$listeners"
      @opened="onOpen"
      @close="onClose"
    >
      <div style="height: 100%">
        <el-row style="height: 100%; overflow: auto">
          <el-col :md="24" :lg="12" class="left-editor">
            <div class="setting" title="资源引用" @click="showResource">
              <el-badge :is-dot="!!resources.length" class="item">
                <i class="el-icon-setting" />
              </el-badge>
            </div>
            <el-tabs v-model="activeTab" type="card" class="editor-tabs">
              <el-tab-pane name="html">
                <span slot="label">
                  <i v-if="activeTab === 'html'" class="el-icon-edit" />
                  <i v-else class="el-icon-document" />
                  template
                </span>
              </el-tab-pane>
              <el-tab-pane name="js">
                <span slot="label">
                  <i v-if="activeTab === 'js'" class="el-icon-edit" />
                  <i v-else class="el-icon-document" />
                  script
                </span>
              </el-tab-pane>
              <el-tab-pane name="css">
                <span slot="label">
                  <i v-if="activeTab === 'css'" class="el-icon-edit" />
                  <i v-else class="el-icon-document" />
                  css
                </span>
              </el-tab-pane>
            </el-tabs>
            <div
              v-show="activeTab === 'html'"
              id="editorHtml"
              class="tab-editor"
            />
            <div v-show="activeTab === 'js'" id="editorJs" class="tab-editor" />
            <div
              v-show="activeTab === 'css'"
              id="editorCss"
              class="tab-editor"
            />
          </el-col>
          <el-col :md="24" :lg="12" class="right-preview">
            <div class="action-bar" :style="{ 'text-align': 'left' }">
              <span class="bar-btn" @click="runCode">
                <i class="el-icon-refresh" />
                刷新
              </span>
              <span class="bar-btn" @click="exportFile">
                <i class="el-icon-download" />
                导出vue文件
              </span>
              <span ref="copyBtn" class="bar-btn copy-btn">
                <i class="el-icon-document-copy" />
                复制代码
              </span>
              <span
                class="bar-btn delete-btn"
                @click="$emit('update:visible', false)"
              >
                <i class="el-icon-circle-close" />
                关闭
              </span>
            </div>
            <iframe
              v-show="isIframeLoaded"
              ref="previewPage"
              class="result-wrapper"
              frameborder="0"
              src="preview.html"
              @load="iframeLoad"
            />
            <div
              v-show="!isIframeLoaded"
              v-loading="true"
              class="result-wrapper"
            />
          </el-col>
        </el-row>
      </div>
    </el-drawer>
    <resource-dialog
      :visible.sync="resourceVisible"
      :origin-resource="resources"
      @save="setResource"
    />
  </div>
</template>
<script>
  import { parse } from '@babel/parser'
  import ClipboardJS from 'clipboard'
  import { saveAs } from 'file-saver'
  import {
    makeUpHtml,
    vueTemplate,
    vueScript,
    cssStyle,
  } from '@/components/generator/html'
  import { makeUpJs } from '@/components/generator/js'
  import { makeUpCss } from '@/components/generator/css'
  import { exportDefault, beautifierConf, titleCase } from '@/utils/form/index'
  import ResourceDialog from './ResourceDialog'
  import loadMonaco from '@/utils/form/loadMonaco'
  import loadBeautifier from '@/utils/form/loadBeautifier'

  const editorObj = {
    html: null,
    js: null,
    css: null,
  }
  const mode = {
    html: 'html',
    js: 'javascript',
    css: 'css',
  }
  let beautifier
  let monaco

  export default {
    components: { ResourceDialog },
    props: ['formData', 'generateConf'],
    data() {
      return {
        activeTab: 'html',
        htmlCode: '',
        jsCode: '',
        cssCode: '',
        codeFrame: '',
        isIframeLoaded: false,
        isInitcode: false, // 保证open后两个异步只执行一次runcode
        isRefreshCode: false, // 每次打开都需要重新刷新代码
        resourceVisible: false,
        scripts: [],
        links: [],
        monaco: null,
      }
    },
    computed: {
      resources() {
        return this.scripts.concat(this.links)
      },
    },
    watch: {},
    created() {},
    mounted() {
      window.addEventListener('keydown', this.preventDefaultSave)
      const clipboard = new ClipboardJS('.copy-btn', {
        text: (trigger) => {
          const codeStr = this.generateCode()
          this.$notify({
            title: '成功',
            message: '代码已复制到剪切板，可粘贴。',
            type: 'success',
          })
          return codeStr
        },
      })
      clipboard.on('error', (e) => {
        this.$message.error('代码复制失败')
      })
    },
    beforeDestroy() {
      window.removeEventListener('keydown', this.preventDefaultSave)
    },
    methods: {
      preventDefaultSave(e) {
        if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
          e.preventDefault()
        }
      },
      onOpen() {
        const { type } = this.generateConf
        this.htmlCode = makeUpHtml(this.formData, type)
        this.jsCode = makeUpJs(this.formData, type)
        this.cssCode = makeUpCss(this.formData)

        loadBeautifier((btf) => {
          beautifier = btf
          this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
          this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
          this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)

          loadMonaco((val) => {
            monaco = val
            this.setEditorValue('editorHtml', 'html', this.htmlCode)
            this.setEditorValue('editorJs', 'js', this.jsCode)
            this.setEditorValue('editorCss', 'css', this.cssCode)
            if (!this.isInitcode) {
              this.isRefreshCode = true
              this.isIframeLoaded && (this.isInitcode = true) && this.runCode()
            }
          })
        })
      },
      onClose() {
        this.isInitcode = false
        this.isRefreshCode = false
      },
      iframeLoad() {
        if (!this.isInitcode) {
          this.isIframeLoaded = true
          this.isRefreshCode && (this.isInitcode = true) && this.runCode()
        }
      },
      setEditorValue(id, type, codeStr) {
        if (editorObj[type]) {
          editorObj[type].setValue(codeStr)
        } else {
          editorObj[type] = monaco.editor.create(document.getElementById(id), {
            value: codeStr,
            theme: 'vs-dark',
            language: mode[type],
            automaticLayout: true,
          })
        }
        // ctrl + s 刷新
        editorObj[type].onKeyDown((e) => {
          if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
            this.runCode()
          }
        })
      },
      runCode() {
        const jsCodeStr = editorObj.js.getValue()
        try {
          const ast = parse(jsCodeStr, { sourceType: 'module' })
          const astBody = ast.program.body
          if (astBody.length > 1) {
            this.$confirm(
              'js格式不能识别，仅支持修改export default的对象内容',
              '提示',
              {
                type: 'warning',
              }
            )
            return
          }
          if (astBody[0].type === 'ExportDefaultDeclaration') {
            const postData = {
              type: 'refreshFrame',
              data: {
                generateConf: this.generateConf,
                html: editorObj.html.getValue(),
                js: jsCodeStr.replace(exportDefault, ''),
                css: editorObj.css.getValue(),
                scripts: this.scripts,
                links: this.links,
              },
            }

            this.$refs.previewPage.contentWindow.postMessage(
              postData,
              location.origin
            )
          } else {
            this.$message.error('请使用export default')
          }
        } catch (err) {
          this.$message.error(`js错误：${err}`)
          console.error(err)
        }
      },
      generateCode() {
        const html = vueTemplate(editorObj.html.getValue())
        const script = vueScript(editorObj.js.getValue())
        const css = cssStyle(editorObj.css.getValue())
        return beautifier.html(html + script + css, beautifierConf.html)
      },
      exportFile() {
        this.$prompt('文件名:', '导出文件', {
          inputValue: `${+new Date()}.vue`,
          closeOnClickModal: false,
          inputPlaceholder: '请输入文件名',
        }).then(({ value }) => {
          if (!value) value = `${+new Date()}.vue`
          const codeStr = this.generateCode()
          const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
          saveAs(blob, value)
        })
      },
      showResource() {
        this.resourceVisible = true
      },
      setResource(arr) {
        const scripts = []
        const links = []
        if (Array.isArray(arr)) {
          arr.forEach((item) => {
            if (item.endsWith('.css')) {
              links.push(item)
            } else {
              scripts.push(item)
            }
          })
          this.scripts = scripts
          this.links = links
        } else {
          this.scripts = []
          this.links = []
        }
      },
    },
  }
</script>

<style lang="scss" scoped>
  @import '@/styles/mixin.scss';
  .tab-editor {
    position: absolute;
    top: 33px;
    bottom: 0;
    left: 0;
    right: 0;
    font-size: 14px;
  }
  .left-editor {
    position: relative;
    height: 100%;
    background: #1e1e1e;
    overflow: hidden;
  }
  .setting {
    position: absolute;
    right: 15px;
    top: 3px;
    color: #a9f122;
    font-size: 18px;
    cursor: pointer;
    z-index: 1;
  }
  .right-preview {
    height: 100%;
    .result-wrapper {
      height: calc(100vh - 33px);
      width: 100%;
      overflow: auto;
      padding: 12px;
      box-sizing: border-box;
    }
  }
  @include action-bar;
  ::v-deep .el-drawer__header {
    display: none;
  }
</style>
